<!DOCTYPE html><html lang="zh" >


<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  
  
  
    <meta name="generator" content="Wowchemy 5.6.0 for Hugo" />
  

  
  












  
  










  







  
  
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
  

  
  
  
    
      
      <link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Merriweather&family=Roboto+Mono&display=swap">
      <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Merriweather&family=Roboto+Mono&display=swap" media="print" onload="this.media='all'">
    
  

  
  
  
  
  
  

  

  
  
  
    
  
  <meta name="description" content="概述 java.nio全称java non-blocking IO，是指JDK1.4 及以" />

  
  <link rel="alternate" hreflang="zh" href="https://songchuanrang.gitee.io/docs/java-nio/" />

  
  
  
    <meta name="theme-color" content="#1565c0" />
  

  
  

  

  <link rel="stylesheet" href="/css/vendor-bundle.min.c7b8d9abd591ba2253ea42747e3ac3f5.css" media="print" onload="this.media='all'">

  
  
  
    
    

    
    
    
    
      
      
    
    
    

    
    
    

    

    
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
      
      

      
      
        
      

      
    
      
      

      
      

      
    
      
      

      
      

      
    
  

  
  
  
  
  
  
  <link rel="stylesheet" href="/css/wowchemy.50151d350c96d255e89f810190000890.css" />

  
  
  
  
  
  
  
    
    
    <link rel="stylesheet" href="/css/libs/chroma/github-light.min.css" title="hl-light" media="print" onload="this.media='all'" >
    <link rel="stylesheet" href="/css/libs/chroma/dracula.min.css" title="hl-dark" media="print" onload="this.media='all'" disabled>
  

  
  



  


  


  




  
  
  

  
  

  
  
    <link rel="manifest" href="/manifest.webmanifest" />
  

  
  <link rel="icon" type="image/png" href="/media/icon_huec9f8681c90e5958ea60dec1a7d5fe8a_15532_32x32_fill_lanczos_center_3.png" />
  <link rel="apple-touch-icon" type="image/png" href="/media/icon_huec9f8681c90e5958ea60dec1a7d5fe8a_15532_180x180_fill_lanczos_center_3.png" />

  <link rel="canonical" href="https://songchuanrang.gitee.io/docs/java-nio/" />

  
  
  
  
  
  
  
  
    
    
  
  

  
  
    
    
  
  <meta property="twitter:card" content="summary" />
  
    <meta property="twitter:site" content="@wowchemy" />
    <meta property="twitter:creator" content="@wowchemy" />
  
  <meta property="og:site_name" content="学习分享" />
  <meta property="og:url" content="https://songchuanrang.gitee.io/docs/java-nio/" />
  <meta property="og:title" content="JAVA NIO | 学习分享" />
  <meta property="og:description" content="概述 java.nio全称java non-blocking IO，是指JDK1.4 及以" /><meta property="og:image" content="https://songchuanrang.gitee.io/media/icon_huec9f8681c90e5958ea60dec1a7d5fe8a_15532_512x512_fill_lanczos_center_3.png" />
    <meta property="twitter:image" content="https://songchuanrang.gitee.io/media/icon_huec9f8681c90e5958ea60dec1a7d5fe8a_15532_512x512_fill_lanczos_center_3.png" /><meta property="og:locale" content="zh" />
  
    
      <meta
        property="article:published_time"
        content="2022-04-12T22:15:59&#43;08:00"
      />
    
    <meta property="article:modified_time" content="2022-04-12T22:15:59&#43;08:00">
  

  



  

  


  <title>JAVA NIO | 学习分享</title>

  
  
  
  











</head>


<body id="top" data-spy="scroll" data-offset="70" data-target="#TableOfContents" class="page-wrapper   " data-wc-page-id="119284351997ad55518d7096323232a5" >

  
  
  
  
  
  
  
  
  
  <script src="/js/wowchemy-init.min.613040fe4f2c0f007b4dcb64404201cb.js"></script>

  


<aside class="search-modal" id="search">
  <div class="container">
    <section class="search-header">

      <div class="row no-gutters justify-content-between mb-3">
        <div class="col-6">
          <h1>搜索</h1>
        </div>
        <div class="col-6 col-search-close">
          <a class="js-search" href="#" aria-label="Close"><i class="fas fa-times-circle text-muted" aria-hidden="true"></i></a>
        </div>
      </div>

      <div id="search-box">
        
        <input name="q" id="search-query" placeholder="搜索..." autocapitalize="off"
        autocomplete="off" autocorrect="off" spellcheck="false" type="search" class="form-control"
        aria-label="搜索...">
        
      </div>

      
      

      

    </section>
    <section class="section-search-results">

      <div id="search-hits">
        
      </div>

    </section>
  </div>
</aside>



  <div class="page-header">
    












<header class="header--fixed">
  <nav class="navbar navbar-expand-lg navbar-light compensate-for-scrollbar" id="navbar-main">
    <div class="container-xl">

      
      <div class="d-none d-lg-inline-flex">
        <a class="navbar-brand" href="/">学习分享</a>
      </div>
      

      

      
      <div class="navbar-brand-mobile-wrapper d-inline-flex d-lg-none">
        <a class="navbar-brand" href="/">学习分享</a>
      </div>
      

      
      
      <div class="navbar-collapse main-menu-item collapse justify-content-start" id="navbar-content">

        
        <ul class="navbar-nav d-md-inline-flex">
          

        

          
        </ul>
      </div>

      <ul class="nav-icons navbar-nav flex-row ml-auto d-flex pl-md-2">

        
        

        
        
        <li class="nav-item">
          <a class="nav-link js-search" href="#" aria-label="搜索"><i class="fas fa-search" aria-hidden="true"></i></a>
        </li>
        

        
        
        
        <li class="nav-item dropdown theme-dropdown">
          <a href="#" class="nav-link" data-toggle="dropdown" aria-haspopup="true" aria-label="Display preferences">
            <i class="fas fa-moon" aria-hidden="true"></i>
          </a>
          <div class="dropdown-menu">
            <a href="#" class="dropdown-item js-set-theme-light">
              <span>浅色</span>
            </a>
            <a href="#" class="dropdown-item js-set-theme-dark">
              <span>深色</span>
            </a>
            <a href="#" class="dropdown-item js-set-theme-auto">
              <span>自动</span>
            </a>
          </div>
        </li>
        

        
        

      </ul>

    </div>
  </nav>
</header>


  </div>

  <div class="page-body">
    
    
    

    




<div class="container-fluid docs">
  <div class="row flex-xl-nowrap">
    <div class="col-12 col-md-3 col-xl-2 docs-sidebar">
      <form class="docs-search d-flex align-items-center">
  <button class="btn docs-toggle d-md-none p-0 mr-md-3 w-100" type="button" data-toggle="collapse" data-target="#docs-nav" aria-controls="docs-nav" aria-expanded="false" aria-label="Toggle section navigation">
    <div class="d-flex">
      <span class="d-md-none pl-1 flex-grow-1 text-left overflow-hidden">
        
        
          Docs
        
      </span>
      <span><i class="fas fa-chevron-down"></i></span>
    </div>
  </button>

  
  <button class="form-control sidebar-search js-search d-none d-md-flex">
    <i class="fas fa-search pr-2"></i>
    <span class="sidebar-search-text">搜索...</span>
    <span class="sidebar-search-shortcut">/</span>
  </button>
  
</form>

<nav class="collapse docs-links" id="docs-nav">
  
  
  
  
  
  

  
  
    





  
    
    
    
    
      
    
    

    
      <div class="docs-toc-item">
        <a class="docs-toc-link " href="/docs/">Docs</a>
    
      
        <ul class="nav docs-sidenav">
      


  <li class=""><a href="/docs/tomcat/">tomcat</a></li>



  <li class=""><a href="/docs/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">设计模式</a></li>



  <li class=""><a href="/docs/centos%E6%8C%82%E8%BD%BDiso%E6%96%87%E4%BB%B6%E4%BD%9C%E4%B8%BAyum%E5%AE%89%E8%A3%85%E6%BA%90/">centos挂载iso文件作为yum安装源</a></li>



  <li class="active"><a href="/docs/java-nio/">JAVA NIO</a></li>



  <li class=""><a href="/docs/div%E5%B8%83%E5%B1%80/">div盒子模型及html布局</a></li>



  <li class=""><a href="/docs/nginx/">nginx安装及配置</a></li>



  <li class=""><a href="/docs/gc%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90gcviewer/">GC日志分析之GCViewer</a></li>

      
        </ul>
      
    

    
      </div>
    

</nav>

    </div>

    
    
    <div class="d-none d-xl-block col-xl-3 docs-toc">
      












      <ul class="nav toc-top">
        <li><a href="#" id="back_to_top" class="docs-toc-title">在本页</a></li>
      </ul>

      <nav id="TableOfContents">
  <ul>
    <li><a href="#概述">概述</a></li>
    <li><a href="#核心组件">核心组件</a>
      <ul>
        <li><a href="#buffer">Buffer</a></li>
        <li><a href="#channel">Channel</a></li>
        <li><a href="#selector">Selector</a></li>
      </ul>
    </li>
    <li><a href="#示例">示例</a>
      <ul>
        <li><a href="#服务端">服务端</a></li>
        <li><a href="#客户端">客户端</a></li>
      </ul>
    </li>
    <li><a href="#参考文献">参考文献</a></li>
  </ul>
</nav>

      











    </div>
    

    <main class="col-12 col-md-9 col-xl-7 py-md-3 pl-md-5 docs-content" role="main">

      <article class="article">

        <div class="docs-article-container">
          
            
  <nav class="d-none d-md-flex" aria-label="breadcrumb">
    <ol class="breadcrumb">
      
  
    
  
    
  

    <li class="breadcrumb-item">
      <a href="/">
        
          Home
        
      </a>
    </li>
  

    <li class="breadcrumb-item">
      <a href="/docs/">
        
          Docs
        
      </a>
    </li>
  

      <li class="breadcrumb-item active" aria-current="page">
        JAVA NIO
      </li>
    </ol>
  </nav>




          
        </div>

        
        

        <div class="docs-article-container">
          
          <h1>JAVA NIO</h1>

          <div class="article-style">

            
            

            <h2 id="概述">概述</h2>
<p>java.nio全称java non-blocking IO，是指JDK1.4 及以上版本里提供的新api（New IO） ，为所有的原始类型（boolean类型除外）提供缓存支持的数据容器，使用它可以提供非阻塞式的高伸缩性网络<br>
与BIO(阻塞式IO)相比，NIO只有在连接/通道真正有读写事件发生时(事件驱动)，才会创建线程进行读写，不必为每一个连接都创建一个线程。这样就大大地减少了系统的开销。</p>
<h2 id="核心组件">核心组件</h2>
<table>
<thead>
<tr>
<th>组件</th>
<th>解释</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>Buffer</td>
<td>缓冲区</td>
<td>存储数据</td>
</tr>
<tr>
<td>Channel</td>
<td>通道</td>
<td>传输数据</td>
</tr>
<tr>
<td>Selecteor</td>
<td>选择器</td>
<td>控制器</td>
</tr>
</tbody>
</table>
<h3 id="buffer">Buffer</h3>
<p>Buffer是一个内存块。在NIO中，所有的数据都是用Buffer处理，有读写两种模式。所以NIO和传统的IO的区别就体现在这里。传统IO是面向Stream流，NIO而是面向缓冲区(Buffer)。<br>
java.nio 定义了以下几个Buffer 的实现</p>
<div class="mermaid">stateDiagram
Buffer --> CharBuffer
Buffer --> ShortBuffer
Buffer --> IntBuffer
Buffer --> LongBuffer
Buffer --> FloatBuffer
Buffer --> DoubleBuffer
Buffer --> ByteBuffer
ByteBuffer --> MappedByteBuffer
</div>
<p>我们经常用到的也是最核心的是ByteBuffer，其余的类只是包装了一下ByteBuffer。<br>
我们应该将 Buffer 理解为一个数组，IntBuffer、CharBuffer、DoubleBuffer 等分别对应 int[]、char[]、double[] 等。
操作 Buffer 和操作数组差不多，只不过大部分时候我们都把它放到了 NIO 的场景里面来使用而已。下面介绍 Buffer 中的几个重要属性和几个重要方法。</p>
<h4 id="positionlimitcapacity">position、limit、capacity</h4>
<p>就像数组有数组容量，每次访问元素要指定下标，Buffer 中也有几个重要属性：position、limit、capacity。<br>
















<figure  >
  <div class="d-flex justify-content-center">
    <div class="w-100" ><img src="/images/ByteBuffer.png" alt="img.png" loading="lazy" data-zoomable /></div>
  </div></figure>
</p>
<ol>
<li>capacity，为缓冲区的容量</li>
<li>position 代表下一次的写入位置，初始值是0，每往Buffer中写入一个值，position就自动加 1。读操作的时候也是类似的，每读一个值，position 就自动加 1。</li>
</ol>
<blockquote>
<p>从写操作模式到读操作模式切换的时候（flip），position 都会归零，这样就可以从头开始读写了。</p>
</blockquote>
<ol start="3">
<li>limit：写操作模式下，limit 代表的是最大能写入的数据，这个时候 limit 等于 capacity。写结束后，切换到读模式，此时的 limit 等于 Buffer 中实际的数据大小，因为 Buffer 不一定被写满了。
















<figure  >
  <div class="d-flex justify-content-center">
    <div class="w-100" ><img src="/images/bytebuffer-2.png" alt="img.png" loading="lazy" data-zoomable /></div>
  </div></figure>
</li>
</ol>
<h4 id="初始化buffer">初始化Buffer</h4>
<p>每个 Buffer 实现类都提供了一个静态方法 allocate(int capacity) 帮助我们快速实例化一个 Buffer。如：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ByteBuffer</span> <span class="n">byteBuf</span> <span class="o">=</span> <span class="n">ByteBuffer</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="mi">1024</span><span class="o">);</span>
</span></span><span class="line"><span class="cl"><span class="n">IntBuffer</span> <span class="n">intBuf</span> <span class="o">=</span> <span class="n">IntBuffer</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="mi">1024</span><span class="o">);</span>
</span></span><span class="line"><span class="cl"><span class="n">LongBuffer</span> <span class="n">longBuf</span> <span class="o">=</span> <span class="n">LongBuffer</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="mi">1024</span><span class="o">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span></code></pre></div><p>另外，我们经常使用 wrap 方法来初始化一个 Buffer。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="n">ByteBuffer</span> <span class="nf">wrap</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">array</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">}</span>
</span></span></code></pre></div><h4 id="填充buffer">填充Buffer</h4>
<p>各个 Buffer 类都提供了一些 put 方法用于将数据填充到 Buffer 中，如 ByteBuffer 中的几个 put 方法：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 填充一个 byte 值
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">public</span> <span class="kd">abstract</span> <span class="n">ByteBuffer</span> <span class="nf">put</span><span class="o">(</span><span class="kt">byte</span> <span class="n">b</span><span class="o">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 在指定位置填充一个 int 值
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">public</span> <span class="kd">abstract</span> <span class="n">ByteBuffer</span> <span class="nf">put</span><span class="o">(</span><span class="kt">int</span> <span class="n">index</span><span class="o">,</span> <span class="kt">byte</span> <span class="n">b</span><span class="o">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 将一个数组中的值填充进去
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">public</span> <span class="kd">final</span> <span class="n">ByteBuffer</span> <span class="nf">put</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">src</span><span class="o">)</span> <span class="o">{...}</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">ByteBuffer</span> <span class="nf">put</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">src</span><span class="o">,</span> <span class="kt">int</span> <span class="n">offset</span><span class="o">,</span> <span class="kt">int</span> <span class="n">length</span><span class="o">)</span> <span class="o">{...}</span>
</span></span></code></pre></div><p>上述这些方法需要自己控制 Buffer 大小，不能超过 capacity，超过会抛 java.nio.BufferOverflowException 异常。<br>
对于 Buffer 来说，另一个常见的操作中就是，我们要将来自 Channel 的数据填充到 Buffer 中，在系统层面上，这个操作我们称为读操作，因为数据是从外部（文件或网络等）读到内存中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">num</span> <span class="o">=</span> <span class="n">channel</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">buf</span><span class="o">);</span>
</span></span></code></pre></div><h4 id="提取-buffer-中的值">提取 Buffer 中的值</h4>
<p>前面介绍了写操作，每写入一个值，position 的值都需要加 1，所以 position 最后会指向最后一次写入的位置的后面一个，如果 Buffer 写满了，那么 position 等于 capacity（position 从 0 开始）。<br>
如果要读 Buffer 中的值，需要切换模式，从写入模式切换到读出模式。注意，通常在说 NIO 的读操作的时候，我们说的是从 Channel 中读数据到 Buffer 中，对应的是对 Buffer 的写入操作，初学者需要理清楚这个。<br>
调用 Buffer 的 flip() 方法，可以从写入模式切换到读取模式。其实这个方法也就是设置了一下 position 和 limit 值罢了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">final</span> <span class="n">Buffer</span> <span class="nf">flip</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">limit</span> <span class="o">=</span> <span class="n">position</span><span class="o">;</span> <span class="c1">// 将 limit 设置为实际写入的数据数量
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">position</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="c1">// 重置 position 为 0
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">mark</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span> <span class="c1">// mark 之后再说
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>对应写入操作的一系列 put 方法，读操作提供了一系列的 get 方法：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 根据 position 来获取数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">byte</span> <span class="nf">get</span><span class="o">();</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 获取指定位置的数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">byte</span> <span class="nf">get</span><span class="o">(</span><span class="kt">int</span> <span class="n">index</span><span class="o">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 将 Buffer 中的数据写入到数组中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">public</span> <span class="n">ByteBuffer</span> <span class="nf">get</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">dst</span><span class="o">)</span>
</span></span></code></pre></div><p>附一个经常使用的方法：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">new</span> <span class="n">String</span><span class="o">(</span><span class="n">buffer</span><span class="o">.</span><span class="na">array</span><span class="o">()).</span><span class="na">trim</span><span class="o">();</span>
</span></span></code></pre></div><p>当然了，除了将数据从 Buffer 取出来使用，更常见的操作是将我们写入的数据传输到 Channel 中，如通过 FileChannel 将数据写入到文件中，通过 SocketChannel 将数据写入网络发送到远程机器等。对应的，这种操作，我们称之为写操作。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">num</span> <span class="o">=</span> <span class="n">channel</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">buf</span><span class="o">);</span>
</span></span></code></pre></div><h4 id="mark--reset">mark() &amp; reset()</h4>
<p>除了 position、limit、capacity 这三个基本的属性外，还有一个常用的属性就是 mark。<br>
mark 用于临时保存 position 的值，每次调用 mark() 方法都会将 mark 设值为当前的 position，便于后续需要的时候使用。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">final</span> <span class="n">Buffer</span> <span class="nf">mark</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mark</span> <span class="o">=</span> <span class="n">position</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>那到底什么时候用呢？考虑以下场景，我们在 position 为 5 的时候，先 mark() 一下，然后继续往下读，读到第 10 的时候，我想重新回到 position 为 5 的地方重新来一遍，那只要调一下 reset() 方法，position 就回到 5 了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">final</span> <span class="n">Buffer</span> <span class="nf">reset</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">m</span> <span class="o">=</span> <span class="n">mark</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">(</span><span class="n">m</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidMarkException</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">position</span> <span class="o">=</span> <span class="n">m</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><h4 id="rewind--clear--compact">rewind() &amp; clear() &amp; compact()</h4>
<p>rewind()：会重置 position 为 0，通常用于重新从头读写 Buffer。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">final</span> <span class="n">Buffer</span> <span class="nf">rewind</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">position</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">mark</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>clear()：有点重置 Buffer 的意思，相当于重新实例化了一样。<br>
通常，我们会先填充 Buffer，然后从 Buffer 读取数据，之后我们再重新往里填充新的数据，我们一般在重新填充之前先调用 clear()。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">final</span> <span class="n">Buffer</span> <span class="nf">clear</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">position</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">limit</span> <span class="o">=</span> <span class="n">capacity</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">mark</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>compact()：和 clear() 一样的是，它们都是在准备往 Buffer 填充新的数据之前调用。<br>
前面说的 clear() 方法会重置几个属性，但是我们要看到，clear() 方法并不会将 Buffer 中的数据清空，只不过后续的写入会覆盖掉原来的数据，也就相当于清空了数据了。<br>
而 compact() 方法有点不一样，调用这个方法以后，会先处理还没有读取的数据，也就是 position 到 limit 之间的数据（还没有读过的数据），先将这些数据移到左边，然后在这个基础上再开始写入。很明显，此时 limit 还是等于 capacity，position 指向原来数据的右边。</p>
<h3 id="channel">Channel</h3>
<p>所有的 NIO 操作始于通道，通道是数据来源或数据写入的目的地，主要地，我们将关心 java.nio 包中实现的以下几个 Channel：</p>
<div class="mermaid">stateDiagram
Channel --> FileChannel
Channel --> SocketChannel
Channel --> DatagramChannel
Channel --> ServerSocketChannel
</div>
<ul>
<li>FileChannel：文件通道，用于文件的读和写</li>
<li>DatagramChannel：用于 UDP 连接的接收和发送</li>
<li>SocketChannel：把它理解为 TCP 连接通道，简单理解就是 TCP 客户端</li>
<li>ServerSocketChannel：TCP 对应的服务端，用于监听某个端口进来的请求</li>
</ul>
<p>这里不是很理解这些也没关系，后面介绍了代码之后就清晰了。还有，我们最应该关注，也是后面将会重点介绍的是 SocketChannel 和 ServerSocketChannel。<br>
Channel 经常翻译为通道，类似 IO 中的流，用于读取和写入。它与前面介绍的 Buffer 打交道，读操作的时候将 Channel 中的数据填充到 Buffer 中，而写操作时将 Buffer 中的数据写入到 Channel 中。<br>
至少读者应该记住一点，这两个方法都是 channel 实例的方法。</p>
<h4 id="socketchannel">SocketChannel</h4>
<p>我们前面说了，我们可以将 SocketChannel 理解成一个 TCP 客户端。虽然这么理解有点狭隘，因为我们在介绍 ServerSocketChannel 的时候会看到另一种使用方式。<br>
打开一个 TCP 连接：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">SocketChannel</span> <span class="n">socketChannel</span> <span class="o">=</span> <span class="n">SocketChannel</span><span class="o">.</span><span class="na">open</span><span class="o">(</span><span class="k">new</span> <span class="n">InetSocketAddress</span><span class="o">(</span><span class="s">&#34;http://localhost&#34;</span><span class="o">,</span> <span class="mi">80</span><span class="o">));</span>
</span></span></code></pre></div><p>当然了，上面的这行代码等价于下面的两行：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 打开一个通道
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">SocketChannel</span> <span class="n">socketChannel</span> <span class="o">=</span> <span class="n">SocketChannel</span><span class="o">.</span><span class="na">open</span><span class="o">();</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 发起连接
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">socketChannel</span><span class="o">.</span><span class="na">connect</span><span class="o">(</span><span class="k">new</span> <span class="n">InetSocketAddress</span><span class="o">(</span><span class="s">&#34;http://localhost&#34;</span><span class="o">,</span> <span class="mi">80</span><span class="o">));</span>
</span></span></code></pre></div><p>SocketChannel 的读写和 FileChannel 没什么区别，就是操作缓冲区。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 读取数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">socketChannel</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">buffer</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 写入数据到网络连接中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">while</span><span class="o">(</span><span class="n">buffer</span><span class="o">.</span><span class="na">hasRemaining</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">socketChannel</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">buffer</span><span class="o">);</span>   
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><h4 id="serversocketchannel">ServerSocketChannel</h4>
<p>之前说 SocketChannel 是 TCP 客户端，这里说的 ServerSocketChannel 就是对应的服务端。<br>
ServerSocketChannel 用于监听机器端口，管理从这个端口进来的 TCP 连接。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 实例化
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">ServerSocketChannel</span> <span class="n">serverSocketChannel</span> <span class="o">=</span> <span class="n">ServerSocketChannel</span><span class="o">.</span><span class="na">open</span><span class="o">();</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 监听 8080 端口
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">serverSocketChannel</span><span class="o">.</span><span class="na">socket</span><span class="o">().</span><span class="na">bind</span><span class="o">(</span><span class="k">new</span> <span class="n">InetSocketAddress</span><span class="o">(</span><span class="mi">8080</span><span class="o">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 一旦有一个 TCP 连接进来，就对应创建一个 SocketChannel 进行处理
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">SocketChannel</span> <span class="n">socketChannel</span> <span class="o">=</span> <span class="n">serverSocketChannel</span><span class="o">.</span><span class="na">accept</span><span class="o">();</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><blockquote>
<p>这里我们可以看到 SocketChannel 的第二个实例化方式</p>
</blockquote>
<p>到这里，我们应该能理解 SocketChannel 了，它不仅仅是 TCP 客户端，它代表的是一个网络通道，可读可写。<br>
ServerSocketChannel 不和 Buffer 打交道了，因为它并不实际处理数据，它一旦接收到请求后，实例化 SocketChannel，之后在这个连接通道上的数据传递它就不管了，因为它需要继续监听端口，等待下一个连接。</p>
<h3 id="selector">Selector</h3>
<p>NIO 三大组件就剩 Selector 了，Selector 建立在非阻塞的基础之上，大家经常听到的 多路复用 在 Java 世界中指的就是它，用于实现一个线程管理多个 Channel。</p>
<ul>
<li>
<p>首先，我们开启一个 Selector。你们爱翻译成选择器也好，多路复用器也好。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Selector</span> <span class="n">selector</span> <span class="o">=</span> <span class="n">Selector</span><span class="o">.</span><span class="na">open</span><span class="o">();</span>
</span></span></code></pre></div></li>
<li>
<p>将 Channel 注册到 Selector 上。前面我们说了，Selector 建立在非阻塞模式之上，所以注册到 Selector 的 Channel 必须要支持非阻塞模式，FileChannel 不支持非阻塞，我们这里讨论最常见的 SocketChannel 和 ServerSocketChannel。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 将通道设置为非阻塞模式，因为默认都是阻塞模式的
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">channel</span><span class="o">.</span><span class="na">configureBlocking</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 注册
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">SelectionKey</span> <span class="n">key</span> <span class="o">=</span> <span class="n">channel</span><span class="o">.</span><span class="na">register</span><span class="o">(</span><span class="n">selector</span><span class="o">,</span> <span class="n">SelectionKey</span><span class="o">.</span><span class="na">OP_READ</span><span class="o">);</span>
</span></span></code></pre></div><p>register 方法的第二个 int 型参数（使用二进制的标记位）用于表明需要监听哪些感兴趣的事件，共以下四种事件：</p>
<ul>
<li>SelectionKey.OP_READ</li>
</ul>
<blockquote>
<p>对应 00000001，通道中有数据可以进行读取</p>
</blockquote>
<ul>
<li>SelectionKey.OP_WRITE</li>
</ul>
<blockquote>
<p>对应 00000100，可以往通道中写入数据</p>
</blockquote>
<ul>
<li>SelectionKey.OP_CONNECT</li>
</ul>
<blockquote>
<p>对应 00001000，成功建立 TCP 连接</p>
</blockquote>
<ul>
<li>SelectionKey.OP_ACCEPT</li>
</ul>
<blockquote>
<p>对应 00010000，接受 TCP 连接</p>
</blockquote>
<p>我们可以同时监听一个 Channel 中的发生的多个事件，比如我们要监听 ACCEPT 和 READ 事件，那么指定参数为二进制的 00010001 即十进制数值 17 即可。<br>
注册方法返回值是 SelectionKey 实例，它包含了 Channel 和 Selector 信息，也包括了一个叫做 Interest Set 的信息，即我们设置的我们感兴趣的正在监听的事件集合。</p>
</li>
<li>
<p>调用 select() 方法获取通道信息。用于判断是否有我们感兴趣的事件已经发生了。</p>
</li>
</ul>
<p>Selector 的操作就是以上 3 步，这里来一个简单的示例，具体可执行的示例会在下一节给出</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Selector</span> <span class="n">selector</span> <span class="o">=</span> <span class="n">Selector</span><span class="o">.</span><span class="na">open</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">channel</span><span class="o">.</span><span class="na">configureBlocking</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">SelectionKey</span> <span class="n">key</span> <span class="o">=</span> <span class="n">channel</span><span class="o">.</span><span class="na">register</span><span class="o">(</span><span class="n">selector</span><span class="o">,</span> <span class="n">SelectionKey</span><span class="o">.</span><span class="na">OP_READ</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// 判断是否有事件准备好
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kt">int</span> <span class="n">readyChannels</span> <span class="o">=</span> <span class="n">selector</span><span class="o">.</span><span class="na">select</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span><span class="o">(</span><span class="n">readyChannels</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// 遍历
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="n">Set</span><span class="o">&lt;</span><span class="n">SelectionKey</span><span class="o">&gt;</span> <span class="n">selectedKeys</span> <span class="o">=</span> <span class="n">selector</span><span class="o">.</span><span class="na">selectedKeys</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">  <span class="n">Iterator</span><span class="o">&lt;</span><span class="n">SelectionKey</span><span class="o">&gt;</span> <span class="n">keyIterator</span> <span class="o">=</span> <span class="n">selectedKeys</span><span class="o">.</span><span class="na">iterator</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">while</span><span class="o">(</span><span class="n">keyIterator</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">SelectionKey</span> <span class="n">key</span> <span class="o">=</span> <span class="n">keyIterator</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span><span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">isAcceptable</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// a connection was accepted by a ServerSocketChannel.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">isConnectable</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// a connection was established with a remote server.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">isReadable</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// a channel is ready for reading
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">isWritable</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// a channel is ready for writing
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">keyIterator</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">  <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>对于 Selector，我们还需要非常熟悉以下几个方法：</p>
<ul>
<li>select()<br>
调用此方法，会将上次 select 之后的准备好的 channel 对应的 SelectionKey 复制到 selected set 中。如果没有任何通道准备好，这个方法会阻塞，直到至少有一个通道准备好。</li>
<li>selectNow()<br>
功能和 select 一样，区别在于如果没有准备好的通道，那么此方法会立即返回 0。</li>
<li>select(long timeout)<br>
看了前面两个，这个应该很好理解了，如果没有通道准备好，此方法会等待一会</li>
<li>wakeup()<br>
这个方法是用来唤醒等待在 select() 和 select(timeout) 上的线程的。如果 wakeup() 先被调用，此时没有线程在 select 上阻塞，那么之后的一个 select() 或 select(timeout) 会立即返回，而不会阻塞，当然，它只会作用一次。</li>
</ul>
<h2 id="示例">示例</h2>
<h3 id="服务端">服务端</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NIOServer</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 打开一个ServerSocketChannel
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">ServerSocketChannel</span> <span class="n">serverSocketChannel</span> <span class="o">=</span> <span class="n">ServerSocketChannel</span><span class="o">.</span><span class="na">open</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">InetSocketAddress</span> <span class="n">address</span> <span class="o">=</span> <span class="k">new</span> <span class="n">InetSocketAddress</span><span class="o">(</span><span class="s">&#34;127.0.0.1&#34;</span><span class="o">,</span> <span class="mi">6666</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 绑定地址
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">serverSocketChannel</span><span class="o">.</span><span class="na">bind</span><span class="o">(</span><span class="n">address</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 设置为非阻塞
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">serverSocketChannel</span><span class="o">.</span><span class="na">configureBlocking</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 打开一个选择器
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">Selector</span> <span class="n">selector</span> <span class="o">=</span> <span class="n">Selector</span><span class="o">.</span><span class="na">open</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// serverSocketChannel注册到选择器中,监听连接事件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">serverSocketChannel</span><span class="o">.</span><span class="na">register</span><span class="o">(</span><span class="n">selector</span><span class="o">,</span> <span class="n">SelectionKey</span><span class="o">.</span><span class="na">OP_ACCEPT</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 循环等待客户端的连接
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 等待3秒，（返回0相当于没有事件）如果没有事件，则跳过
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">if</span> <span class="o">(</span><span class="n">selector</span><span class="o">.</span><span class="na">select</span><span class="o">(</span><span class="mi">3000</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;服务器等待3秒，没有连接&#34;</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">            <span class="o">}</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 如果有事件selector.select(3000)&gt;0的情况,获取事件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">Set</span><span class="o">&lt;</span><span class="n">SelectionKey</span><span class="o">&gt;</span> <span class="n">selectionKeys</span> <span class="o">=</span> <span class="n">selector</span><span class="o">.</span><span class="na">selectedKeys</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 获取迭代器遍历
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">Iterator</span><span class="o">&lt;</span><span class="n">SelectionKey</span><span class="o">&gt;</span> <span class="n">it</span> <span class="o">=</span> <span class="n">selectionKeys</span><span class="o">.</span><span class="na">iterator</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">while</span> <span class="o">(</span><span class="n">it</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 获取到事件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="n">SelectionKey</span> <span class="n">selectionKey</span> <span class="o">=</span> <span class="n">it</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 判断如果是连接事件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="k">if</span> <span class="o">(</span><span class="n">selectionKey</span><span class="o">.</span><span class="na">isAcceptable</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// 服务器与客户端建立连接，获取socketChannel
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="n">SocketChannel</span> <span class="n">socketChannel</span> <span class="o">=</span> <span class="n">serverSocketChannel</span><span class="o">.</span><span class="na">accept</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// 设置成非阻塞
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="n">socketChannel</span><span class="o">.</span><span class="na">configureBlocking</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// 把socketChannel注册到selector中，监听读事件，并绑定一个缓冲区
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="n">socketChannel</span><span class="o">.</span><span class="na">register</span><span class="o">(</span><span class="n">selector</span><span class="o">,</span> <span class="n">SelectionKey</span><span class="o">.</span><span class="na">OP_READ</span><span class="o">,</span> <span class="n">ByteBuffer</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="mi">1024</span><span class="o">));</span>
</span></span><span class="line"><span class="cl">                <span class="o">}</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 如果是读事件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="k">if</span> <span class="o">(</span><span class="n">selectionKey</span><span class="o">.</span><span class="na">isReadable</span><span class="o">())</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">//获取通道
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="n">SocketChannel</span> <span class="n">socketChannel</span> <span class="o">=</span> <span class="o">(</span><span class="n">SocketChannel</span><span class="o">)</span> <span class="n">selectionKey</span><span class="o">.</span><span class="na">channel</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// 获取关联的ByteBuffer
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="n">ByteBuffer</span> <span class="n">buffer</span> <span class="o">=</span> <span class="o">(</span><span class="n">ByteBuffer</span><span class="o">)</span> <span class="n">selectionKey</span><span class="o">.</span><span class="na">attachment</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// 打印从客户端获取到的数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="n">socketChannel</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">buffer</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">                    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;from 客户端：&#34;</span> <span class="o">+</span> <span class="k">new</span> <span class="n">String</span><span class="o">(</span><span class="n">buffer</span><span class="o">.</span><span class="na">array</span><span class="o">()));</span>
</span></span><span class="line"><span class="cl">                <span class="o">}</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 从事件集合中删除已处理的事件，防止重复处理
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="n">it</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">            <span class="o">}</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><h3 id="客户端">客户端</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NIOClient</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">SocketChannel</span> <span class="n">socketChannel</span> <span class="o">=</span> <span class="n">SocketChannel</span><span class="o">.</span><span class="na">open</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">InetSocketAddress</span> <span class="n">address</span> <span class="o">=</span> <span class="k">new</span> <span class="n">InetSocketAddress</span><span class="o">(</span><span class="s">&#34;127.0.0.1&#34;</span><span class="o">,</span> <span class="mi">6666</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">socketChannel</span><span class="o">.</span><span class="na">configureBlocking</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 连接服务器
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="kt">boolean</span> <span class="n">connect</span> <span class="o">=</span> <span class="n">socketChannel</span><span class="o">.</span><span class="na">connect</span><span class="o">(</span><span class="n">address</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 判断是否连接成功
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span><span class="o">(!</span><span class="n">connect</span><span class="o">){</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 等待连接的过程中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">while</span> <span class="o">(!</span><span class="n">socketChannel</span><span class="o">.</span><span class="na">finishConnect</span><span class="o">()){</span>
</span></span><span class="line"><span class="cl">                <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;连接服务器需要时间，期间可以做其他事情...&#34;</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">            <span class="o">}</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">String</span> <span class="n">msg</span> <span class="o">=</span> <span class="s">&#34;hello world！&#34;</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">ByteBuffer</span> <span class="n">byteBuffer</span> <span class="o">=</span> <span class="n">ByteBuffer</span><span class="o">.</span><span class="na">wrap</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 把byteBuffer数据写入到通道中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">socketChannel</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">byteBuffer</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 让程序卡在这个位置，不关闭连接
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">in</span><span class="o">.</span><span class="na">read</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><h2 id="参考文献">参考文献</h2>
<blockquote>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzU1OTgzNTAzNQ==&amp;mid=2247483835&amp;idx=1&amp;sn=03d35c05c514ca671331e7e117684143&amp;chksm=fc10734fcb67fa59b5f0977c91d4a70212acb1bdff46fb5770e1bfb52f425c3bb0753d3e6403&amp;scene=178&amp;cur_album_id=1445826382487207937#rd" target="_blank" rel="noopener">NIO从入门到踹门</a><br>
<a href="https://www.javadoop.com/post/java-nio" target="_blank" rel="noopener">Java 非阻塞 IO 和异步 IO</a></p>
</blockquote>


          </div>

          



          
          
          <div class="article-widget">
            
<div class="post-nav">
  
  
  
  <div class="post-nav-item">
    <div class="meta-nav">上一页</div>
    <a href="/docs/gc%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90gcviewer/" rel="next">GC日志分析之GCViewer</a>
  </div>
  
  
  
  <div class="post-nav-item">
    <div class="meta-nav">下一页</div>
    <a href="/docs/nginx/" rel="prev">nginx安装及配置</a>
  </div>
  
</div>

          </div>
          
        </div>

        <div class="body-footer">
          <p>最近更新于 2022/4/12</p>

          




          




          


        </div>

      </article>

      <footer class="site-footer">

  












  

  

  

  
  






  
  
  

  
  
    
  
  
    
  

  

  
  <p class="powered-by copyright-license-text">
    © 2023 songchuanrang. This work is licensed under <a href="https://creativecommons.org/licenses/by-nc-nd/4.0" rel="noopener noreferrer" target="_blank">CC BY NC ND 4.0</a>
  </p>
  

  <p class="powered-by footer-license-icons">
    <a href="https://creativecommons.org/licenses/by-nc-nd/4.0" rel="noopener noreferrer" target="_blank" aria-label="Creative Commons">
      <i class="fab fa-creative-commons fa-2x" aria-hidden="true"></i>
      <i class="fab fa-creative-commons-by fa-2x" aria-hidden="true"></i>
      
        <i class="fab fa-creative-commons-nc fa-2x" aria-hidden="true"></i>
      
      
        <i class="fab fa-creative-commons-nd fa-2x" aria-hidden="true"></i>
      
    </a>
  </p>




  <p class="powered-by">
    
    
    
      
      
      
      
      
      
      由<a href="https://wowchemy.com/?utm_campaign=poweredby" target="_blank" rel="noopener">Wowchemy</a>支持发布——免费<a href="https://github.com/wowchemy/wowchemy-hugo-themes" target="_blank" rel="noopener">开源</a>网站，为创作者赋能。
    
  </p>
</footer>


    </main>
  </div>
</div>

  </div>

  <div class="page-footer">
    
    
  </div>

  


<script src="/js/vendor-bundle.min.32ee83730ed883becad04bc5170512cc.js"></script>




  

  
  

  
    
    <script type="module" title="mermaid">
      import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
      window.mermaid = mermaid;
    </script>
  









<script src="https://cdn.jsdelivr.net/npm/anchor-js@5.0.0/anchor.min.js" integrity="sha256-aQmOEF2ZD4NM/xt4hthzREIo/2PFkOX/g01WjxEV7Ys=" crossorigin="anonymous"></script>
<script>
  anchors.add();
</script>





  
  <script id="search-hit-fuse-template" type="text/x-template">
    <div class="search-hit" id="summary-{{key}}">
      <div class="search-hit-content">
        <div class="search-hit-name">
          <a href="{{relpermalink}}">{{title}}</a>
          <div class="article-metadata search-hit-type">{{type}}</div>
          <p class="search-hit-description">{{snippet}}</p>
        </div>
      </div>
    </div>
  </script>
  
    <script src="https://cdn.jsdelivr.net/gh/krisk/Fuse@v3.2.1/dist/fuse.min.js" integrity="sha512-o38bmzBGX+hD3JHWUFCDA09btWaqrNmoJ3RXLlrysA7PP01Kgs4UlE4MhelE1v5dJR3+cxlR4qQlotsW7jKsnw==" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/gh/julmot/mark.js@8.11.1/dist/jquery.mark.min.js" integrity="sha512-mhbv5DqBMgrWL+32MmsDOt/OAvqr/cHimk6B8y/bx/xS88MVkYGPiVv2ixKVrkywF2qHplNRUvFsAHUdxZ3Krg==" crossorigin="anonymous"></script>
  












  
  
  
  
  
  
  







<script id="page-data" type="application/json">{"use_headroom":false}</script>











  
  


<script src="/zh/js/wowchemy.min.4e5e27caa14e13784384b84ea7186f46.js"></script>























</body>
</html>
